#include "communicator.h"

Communicator::Communicator(SFMLWindow *sf, QObject *parent) :
    QObject(parent)
{
    my_ip = sf::IpAddress::getLocalAddress();
    serv_ip = sf::IpAddress::None;
    str_ip = sf::IpAddress::None;

    slotTimer = new QTimer(this);
    slotTimer->start(10);

    lifeTimer = new QTimer(this);
    lifeTimer->start(UPDATE_LIFETIME);

    amIServer = false;
    inLobby = false;
    id_count = 10;

    name = "WorldEdit";
    sfml = sf;

    udpSocket.setBlocking(false);
    if (udpSocket.bind(sf::UdpSocket::AnyPort) == sf::Socket::Error)
        qDebug() << "Error binding socket (may be port is busy)";
    my_port = udpSocket.getLocalPort();

    QString num;
    num.setNum(my_port);
    emit consoleOut("My ip: " + QString::fromStdString(my_ip.toString()) + " port: " + num, Qt::darkGreen);
    qDebug() << "My ip: " + QString::fromStdString(my_ip.toString()) + " port: " + num;


    selector.add(udpSocket);

    connect(slotTimer, SIGNAL(timeout()), this, SLOT(checkSocket()));
}


QString Communicator::getPlayerName(sf::IpAddress ip, sf::Uint16 port) {

    std::vector<plInLobby>::iterator it = list.begin();
    for (; it != list.end(); it++) {
        if (((*it).adr == ip)&&((*it).port == port))
            return (*it).name;
    }

    return "???";
}

void Communicator::solveName(sf::IpAddress ip, sf::Uint16 port, QString nm) {

    std::vector<plInLobby>::iterator it = list.begin();
    for (; it != list.end(); it++) {
        if (((*it).adr == ip)&&((*it).port == port)) {
            (*it).name = nm;
            emit playerListChanged(&list);
            return;
        }
    }

}

bool Communicator::addPlayer(sf::IpAddress ip, sf::Uint16 port) {

    std::vector<plInLobby>::iterator it = list.begin();
    for (; it != list.end(); it++) {
        if (((*it).adr == ip)&&((*it).port == port))
            return false;
    }

    plInLobby player;
    player.adr = ip;
    player.port = port;
    player.name = "";
    player.lifetime = START_LIFETIME;

    list.push_back(player);

    emit playerListChanged(&list);

    return true;
}

void Communicator::addMsgToStack(sf::IpAddress toAdr, sf::Uint16 port, sf::Packet pack) {
    Msg* msg = new Msg(toAdr, port, pack);
    outStack.push(msg);
}

void Communicator::askConnectionToServer(sf::IpAddress ip) {
    Msg* msg = new Msg(ip, SERVER_PORT, cr_Connect_msg());
    outStack.push(msg);

    consoleOut("Try connection to server" + QString::fromStdString((ip.toString())), Qt::yellow);
}

void Communicator::askServerInfo(sf::IpAddress ip) {
    Msg* msg = new Msg(ip, SERVER_PORT, cr_IsServer_msg());
    outStack.push(msg);
}

void Communicator::sendServInfo(sf::IpAddress ip, sf::Uint16 port) {
    Msg* msg = new Msg(ip, port, cr_ServInfo_msg(name, list.size()));
    outStack.push(msg);
}

void Communicator::refreshServerList() {

    QString num;
    num.setNum(my_port);
    statusOut("My ip: " + QString::fromStdString(my_ip.toString()) + " port: " + num);

    while(!serverList.empty())
        serverList.pop_back();

    emit servListChanged(&serverList);

    askServerInfo(sf::IpAddress::Broadcast);

    checkOutStack();
}


void Communicator::checkOutStack() {

    while (!outStack.empty()) {
        //emit consoleOut("Send pack to " + QString::fromStdString((outStack.top()->ip).toString()) + "(" + QString().setNum(outStack.top()->port) + ")", Qt::gray);
        udpSocket.send(outStack.top()->msg, outStack.top()->ip, outStack.top()->port);

        outStack.pop();
    }

}

void Communicator::checkInStack() {

    while (!outStack.empty()) {

    }

}

void Communicator::checkSocket() {


    if (selector.wait(sf::milliseconds(1))) {
        if (selector.isReady(udpSocket)) {
            //udpSocket.setBlocking(true);

            sf::Packet pack;
            uchar ch = '*';
            sf::IpAddress adr;
            unsigned short port;

            while (udpSocket.receive(pack, adr, port) == sf::UdpSocket::Done) {
                pack >> ch;

                QString adrStr = QString::fromStdString(adr.toString());
                QString portStr;
                portStr.setNum(port);

                //if (ch != 'k')
                //    emit consoleOut("Catch packet from " + adrStr + " (" + portStr + ") {" + ch + "}", Qt::gray);

                if (!amIServer) { //===========  Client  ===============================
                    if      (ch == 'n') {
                        pack = cr_PlInfo_msg(my_ip, my_port, name);
                        addMsgToStack(adr, port, pack);

                        //emit consoleOut("catch PlayerInfo request from" + adrStr, Qt::gray);
                    }
                    else if (ch == 'k') {

                        if (!inLobby) {
                            serv_ip = adr;

                            comeToLobby();
                            srv_lifetime = START_LIFETIME;
                            connect(lifeTimer, SIGNAL(timeout()), this, SLOT(checkNames()));
                            inLobby = true;


                        }

                        if (adr == serv_ip) {

                            srv_lifetime = START_LIFETIME;

                            pack = cr_Ok_msg();
                            addMsgToStack(adr, port, pack);

                            //addMsgToStack(serv_ip, SERVER_PORT ,cr_Tank_msg(sfml->myTank->hp, sfml->myTank->sprite.getPosition().x,
                            //                                sfml->myTank->sprite.getPosition().y, sfml->myTank->sprite.getRotation(),
                            //                                sfml->myTank->gunSprite.getRotation(), sfml->myTank->color));
                        }

                        //emit consoleOut("Catch pinq request from (" + adrStr + ')', Qt::gray);
                    }
                    else if (ch == 's') {
                        QString hisname;
                        sf::Uint8 count;
                        open_ServInfo_msg(pack, hisname, count);
                        QString num;
                        num.setNum(count, 10);

                        Host host;
                        host.ip = adr;
                        host.name = hisname;
                        host.plCount = count;

                        addServerToList(host);
                        emit servListChanged(&serverList);

                        emit consoleOut("ServInfo from " + adrStr + " (" + hisname + ") " + num + "/10", Qt::darkGreen);
                    }
                    else if (ch == 'm') {
                        QString msg;

                        open_msg_string(pack, msg);
                        open_msg_adr(pack, adr, port);

                        emit consoleOut(" " + getPlayerName(adr, port) + ": " + msg, Qt::darkMagenta);
                    }
                    else if (ch == 'p') {
                        if ((adr != my_ip)||(port != my_port)) {
                            QString hisname;
                            sf::IpAddress hisIp;
                            sf::Uint16 hisPort;

                            open_PlInfo_msg(pack, hisIp, hisPort, hisname);

                            solveName(hisIp, hisPort, hisname);
                            emit playerListChanged(&list);

                            emit consoleOut("Solve name -> " + hisname, Qt::gray);
                        }
                    }
                    else if (ch == 't') {
                        open_PlList_msg(pack, &list);
                        QString num;
                        num.setNum(list.size());

                        emit playerListChanged(&list);

                        emit consoleOut("plList updated (" + num + ")", Qt::gray);
                    }
                    else if (ch == 'i') {
                        sf::Int16 color;
                        pack >> color;
                        QString num;
                        num.setNum(color);

                        sfml->changeColor(color);

                        emit consoleOut("My color now is " + num, Qt::yellow);
                    }
                    else if (ch == 'g') {
                        emit startGameSignal();
                        startGame();
                        sfml->start();

                        emit consoleOut("Game started!", Qt::green);
                    }
                }
                else {  //===================  Server  ================================
                    if (ch == 'c') {
                        if (list.size() < MAX_PLAYERS) {

                            addMsgToStack(adr, port, cr_Ok_msg());
                            addMsgToStack(adr, port, cr_Name_msg());

                            qDebug() << "!!";

                            addPlayer(adr, port);

                            broadcastColors();

                            emit consoleOut("Accept connection for client (" + adrStr + ')', Qt::darkGreen);
                        }
                    }
                    else if (ch == '?') {
                        if ((adr != my_ip)||(port != my_port)) {

                            sendServInfo(adr, port);

                            emit consoleOut("Send my Info to " + adrStr, Qt::gray);
                        }
                    }
                    else if (ch == 'n') {
                        sf::IpAddress hisIp;
                        sf::Uint16 hisPort;
                        open_msg_adr(pack, hisIp, hisPort);

                        addMsgToStack(adr, port, cr_PlInfo_msg(hisIp, hisPort, getPlayerName(hisIp, hisPort)));

                        //emit consoleOut("name resolution request from" + adrStr, Qt::gray);
                    }
                    else if (ch == 'k') {

                        lifeUpPlayer(adr, port);
                        //emit consoleOut("OkPing from (" + adrStr + ") " + getPlayerName(adr, port), Qt::gray);
                    }
                    else if (ch == 'p') {
                        if ((adr != my_ip)||(port != my_port)) {
                            QString msg;
                            open_msg_string(pack, msg);

                            solveName(adr, port, msg);

                            emit consoleOut("Solve " + adrStr + " -> " + msg, Qt::gray);
                        }
                    }
                    else if (ch == 'm') {
                        QString msg;

                        open_msg_string(pack, msg);
                        open_msg_adr(pack, adr, port);

                        emit consoleOut("Catch chat msg from " + adrStr + ". Broadcast it.", Qt::gray);
                        emit consoleOut(" " + getPlayerName(adr, port) + ": " + msg, Qt::darkMagenta);
                        broadcastMsg(adr, port, msg);

                    }

                }

                if (ch == 'z') {
                    tankInfo tank;
                    open_Tank_msg(pack, tank);

                    if(tank.color == sfml->my_color) {
                        sfml->myTank->hp = tank.hp;
                        //emit consoleOut("Tank (mytank)", Qt::green);
                        return;
                    }

                    sfml->tankHere(tank);

                }

                if (ch == 'o') {
                    shoot sh;
                    open_Shoot_msg(pack, sh);

                    if (sh.x > 900)
                            qDebug() << "!";

                    sfml->shootHere(sh);

                    //consoleOut("shoot priehal", Qt::gray);
                }

            }
        }

    }

    //udpSocket.setBlocking(false);
    checkOutStack();
    //else
    //     emit consoleOut("Catch nothing", Qt::gray);

}

void Communicator::becomeServer(bool) {

    udpSocket.unbind();
    if (udpSocket.bind(SERVER_PORT) != sf::UdpSocket::Done) {
        emit consoleOut("Error binding port", Qt::darkRed);
        return;
    }

    my_port = SERVER_PORT;

    amIServer = true;
    addPlayer(my_ip, my_port);
    solveName(my_ip, my_port, name);
    connect(lifeTimer, SIGNAL(timeout()), this, SLOT(updateLifetime()));
    connect(this, SIGNAL(playerListChanged(std::vector<plInLobby>*)), this, SLOT(broadcastPlList()));

    QString num;
    num.setNum(my_port);
    statusOut("My ip: " + QString::fromStdString(my_ip.toString()) + " port: " + num);

    emit consoleOut("Game is hosted.", Qt::darkGreen);
}

void Communicator::addServerToList(Host host) {

    std::vector<Host>::iterator it = serverList.begin();
    for (;it != serverList.end(); it++) {
        if ((*it).ip == host.ip)
            return;
    }

    serverList.push_back(host);

}

void Communicator::changeName(const QString &text) {
    name = text;
}

void Communicator::tryToConnect(sf::IpAddress ip) {
    askConnectionToServer(ip);
}

bool Communicator::isServer() {
    return amIServer;
}

void Communicator::chatMsg(QString msg) {

    emit consoleOut(" " + name + ": " + msg, Qt::darkMagenta);

    if (amIServer) {
        broadcastMsg(my_ip, my_port, msg);
    }
    else {
        addMsgToStack(serv_ip, SERVER_PORT, cr_Chat_msg(my_ip, my_port, msg));
    }
}

void Communicator::broadcastMsg(sf::IpAddress ip, sf::Uint16 port, QString msg) {

    std::vector<plInLobby>::iterator it = list.begin();
    for (; it != list.end(); it++) {
        if ( (((*it).adr != my_ip)||((*it).port != my_port))&&
            (((*it).adr != ip)||((*it).port != port)) )
            addMsgToStack((*it).adr, (*it).port, cr_Chat_msg(ip, port, msg));
    }
}

void Communicator::updateLifetime() {

    if (list.size() == 1)
        return;

    std::vector<plInLobby>::iterator it = list.begin();
    for (; it != list.end(); it++) {
        if (((*it).adr != my_ip)||((*it).port != my_port)) {
            (*it).lifetime -= UPDATE_LIFETIME;

            if ((*it).lifetime <= 0) {
                it = list.erase(it);
                emit playerListChanged(&list);
                broadcastColors();
                return;
            }
            else {
                if ((*it).name == "")
                    addMsgToStack((*it).adr, (*it).port, cr_Name_msg());

                addMsgToStack((*it).adr, (*it).port, cr_Ok_msg());
            }
        }
    }
}

void Communicator::lifeUpPlayer(sf::IpAddress ip, sf::Uint16 port) {

    std::vector<plInLobby>::iterator it = list.begin();
    for (; it != list.end(); it++) {
        if (((*it).adr == ip)&&((*it).port == port))
            (*it).lifetime = START_LIFETIME;
    }

}

void Communicator::broadcastPlList() {

    std::vector<plInLobby>::iterator it = list.begin();
    for (; it != list.end(); it++) {
        if (((*it).adr != my_ip)||((*it).port != my_port))
            addMsgToStack((*it).adr, (*it).port, cr_PlList_msg(&list));
    }
}

void Communicator::checkNames() {

    srv_lifetime -= UPDATE_LIFETIME;
    if (srv_lifetime < 0) {
        leaveGame();
        return;
    }

    std::vector<plInLobby>::iterator it = list.begin();
    for (; it != list.end(); it++) {
        if ((*it).name == "") {
            addMsgToStack(serv_ip, SERVER_PORT, cr_Name_msg((*it).adr, (*it).port));
        }
    }

}

void Communicator::leaveGame() {

    amIServer = false;

    serv_ip = sf::IpAddress::None;
    inLobby = false;
    disconnect(lifeTimer, SIGNAL(timeout()), this, SLOT(checkNames()));
    disconnect(slotTimer, SIGNAL(timeout()), this, SLOT(gameLoop()));

    while (list.size() > 0)
        list.pop_back();

    emit leaveLobby();

}

void Communicator::broadcastColors() {

    sf::Int16 id = 0;
    std::vector<plInLobby>::iterator it = list.begin();
    for (; it != list.end(); it++) {
        if (((*it).adr != my_ip)||((*it).port != my_port)) {
            addMsgToStack((*it).adr, (*it).port, cr_id_msg(id));
        }
        id++;
    }
}

void Communicator::startGame() {

    while (sfml->tanks.size() > 0)
        sfml->tanks.pop_back();

    connect(lifeTimer, SIGNAL(timeout()), this, SLOT(gameLoop()));

    if (amIServer) {
        std::vector<plInLobby>::iterator it = list.begin();
        for (; it != list.end(); it++) {
            if (((*it).adr != my_ip)||((*it).port != my_port)) {
                addMsgToStack((*it).adr, (*it).port, cr_Go_msg());
            }
        }

        emit colorChanged(0);
    }



}


void Communicator::gameLoop() {

    if (amIServer)
    {
        playShoots();

        std::vector<Panzer*>::iterator it = sfml->tanks.begin();

        for(;it != sfml->tanks.end(); it++)
        {
            if ((*it)->hp < 0)
                (*it)->hp ++;
            else if ((*it)->hp == 0)
                (*it)->hp = 100;
        }

        if (list.size() <= 1)
            return;

        std::vector<plInLobby>::iterator itt = list.begin();
        short count = 0;
        itt++;
        for (; itt != list.end(); itt++)
        {
            count++;


            for(it = sfml->tanks.begin(); it != sfml->tanks.end(); it++)
            {
                addMsgToStack((*itt).adr, (*itt).port, cr_Tank_msg((*it)->hp, (*it)->sprite.getPosition().x,
                               (*it)->sprite.getPosition().y, (*it)->sprite.getRotation(),
                             (*it)->gunSprite.getRotation(), (*it)->color));

            }

            std::vector<shoot>::iterator sh = sfml->shoots.begin();
            for(;sh != sfml->shoots.end(); sh++)
            {
                addMsgToStack((*itt).adr, (*itt).port, cr_Shoot_msg((*sh).x, (*sh).y, (*sh).alpha,
                                                                    (*sh).color, (*sh).id));
            }
        }
    }
    else //client
    {
        addMsgToStack(serv_ip, SERVER_PORT ,cr_Tank_msg(sfml->myTank->hp, sfml->myTank->sprite.getPosition().x,
                    sfml->myTank->sprite.getPosition().y, sfml->myTank->sprite.getRotation(),
                    sfml->myTank->gunSprite.getRotation(), sfml->myTank->color));
    }
}

void Communicator::playShoots() {

    std::vector<shoot>::iterator sh = sfml->shoots.begin();
    std::vector<Panzer*>::iterator it = sfml->tanks.begin();

    while(sh != sfml->shoots.end())
    {
        (*sh).x = (*sh).x + qCos(((*sh).alpha+180)*2*3.14/360)*8;
        (*sh).y = (*sh).y + qSin(((*sh).alpha+180)*2*3.14/360)*8;

//        if ((*sh).x > WIDTH) { //ХИТРОТА
//            if (qrand()%2 == 0)
//               (*sh).alpha = (*sh).alpha - 180 + 2*(*sh).alpha;
//        }

        if (sfml->myTank->sprite.getGlobalBounds().contains((float)(*sh).x, (float)(*sh).y))
        {
            if (sfml->myTank->hp > 0)
            {
                sfml->myTank->hp -= 25;
                if (sfml->myTank->hp <= 0)
                    sfml->myTank->hp = -30;
            }

            //Destroy shoot
            //Destroy shoot
            std::vector<plInLobby>::iterator pl = list.begin();
            for (; pl != list.end(); pl++) {
                qDebug() << "send del shoot";
                addMsgToStack((*pl).adr, (*pl).port, cr_Shoot_msg(1000, 1000, (*sh).alpha, (*sh).color, (*sh).id));
            }
            sh = sfml->shoots.erase(sh);

            if (sh == sfml->shoots.end()) return;
        }


        for(it = sfml->tanks.begin(); it != sfml->tanks.end();)
        {
            //qDebug() << "(" << (*sh).x << ", " << (*sh).x << ") : Tnak[" << (*it)->sprite.getGlobalBounds().left << " "
            //         << (*it)->sprite.getGlobalBounds().top << " " << (*it)->sprite.getGlobalBounds().width
            //         << " " << (*it)->sprite.getGlobalBounds().height;
            if ((*it)->sprite.getGlobalBounds().contains((float)(*sh).x, (float)(*sh).y))
            {
                //КОЛЛИЗИЯ

                //DD
                if ((*it)->hp > 0)
                {
                    (*it)->hp -= 25;
                    if ((*it)->hp <= 0)
                        (*it)->hp = -30;
                }

                //Destroy shoot
                std::vector<plInLobby>::iterator pl = list.begin();
                for (; pl != list.end(); pl++) {
                    qDebug() << "send del shoot";
                    addMsgToStack((*pl).adr, (*pl).port, cr_Shoot_msg(1000, 1000, (*sh).alpha, (*sh).color, (*sh).id));
                }

                sh = sfml->shoots.erase(sh);
                it = sfml->tanks.end();
            }
            else
            {
                //НЕ_КОЛЛИЗИЯ
                it++;
            }
        }

        if (sh != sfml->shoots.end())
            sh++;
    }

}

void Communicator::sendShoot(shoot sh) {

    qDebug() << "send Shoot";

    if (!amIServer) {
        qDebug() << "sended";
        addMsgToStack(serv_ip, SERVER_PORT, cr_Shoot_msg(sh.x, sh.y, sh.alpha, sh.color, sh.id));
    }
}

void Communicator::changeStrIp(const QString &text) {

    str_ip = sf::IpAddress(text.toStdString());
    //qDebug() << QString::fromStdString(str_ip.toString());
}

void Communicator::strConnection() {
    tryToConnect(str_ip);
}

